# coding: utf-8
from __future__ import division
from shapely.geometry import Polygon
import math


class Node(object):
    def __init__(self, lat, lng):
        """
        lng for longitude
        lat for latitude
        不支持跨经度分界线
        排序按照从北到南（即纬度大小反过来），从西到东（经度大小）
        西经、南纬使用负数
        """
        self.lat = lat
        self.lng = lng

    def __eq__(self, position2):
        return (self.lat == position2.lat and self.lng == position2.lng)

    def __ne__(self, position2):
        return (self.lat != position2.lat or self.lng != position2.lng)

    def __gt__(self, position2):
        return (self.lat < position2.lat) or (self.lat == position2.lat and self.lng > position2.lng)

    def __lt__(self, position2):
        return (self.lat > position2.lat) or (self.lat == position2.lat and self.lng < position2.lng)

    def __le__(self, position2):
        return ((self.lat > position2.lat) or (self.lat == position2.lat and self.lng < position2.lng) or
                (self.lat == position2.lat and self.lng == position2.lng))

    def __ge__(self, position2):
        return ((self.lat < position2.lat) or (self.lat == position2.lat and self.lng > position2.lng) or
                (self.lat == position2.lat and self.lng == position2.lng))

    def __str__(self):
        return str(self.lat) + ',' + str(self.lng)

    def dist(self, position2):
        return (abs(self.lat - position2.lat) + abs(self.lng - position2.lng))

    def copy(self):
        return Node(self.lat, self.lng)

    def calDist(self, node2):
        """
        generally used geo measurement function
        用米返回的距离
        """
        R = 6378.137  # Radius of earth in KM
        dLat = node2.lat * math.pi / 180 - self.lat * math.pi / 180
        dLon = node2.lng * math.pi / 180 - self.lng * math.pi / 180
        a = math.sin(dLat / 2) * math.sin(dLat / 2) + \
            math.cos(self.lat * math.pi / 180) * math.cos(node2.lat * math.pi / 180) * \
            math.sin(dLon / 2) * math.sin(dLon / 2)
        c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
        d = R * c
        return d * 1000  # meters

    def in_(self, region_or_way_or_circle):
        if isinstance(region_or_way_or_circle, Region):
            region = region_or_way_or_circle
            return (self.lat <= region.left_up.lat and self.lat >= region.right_down.lat) \
                and (self.lng >= region.left_up.lng and self.lng <= region.right_down.lng)
        elif isinstance(region_or_way_or_circle, Way):
            way = region_or_way_or_circle
            verts = [(node.lng, node.lat) for node in way.nodes]
            return point_in_polygon(self.lng, self.lat, verts)
        elif isinstance(region_or_way_or_circle, Circle):
            circle = region_or_way_or_circle
            distance = self.dist(circle.center)  # in meters
            return distance <= circle.r
        else:
            raise NotImplementedError


class Circle(object):
    """
    r：单位为米
    """

    def __init__(self, center_node, r):
        earthR = 6378137.0
        self.center = center_node.copy()
        self.left_up_corner = Node(
            lat=center_node.lat + (r / earthR) * (180 / math.pi),
            lng=center_node.lng - (r / earthR) * (180 / math.pi) / math.cos(center_node.lat * math.pi / 180)
        )
        self.right_down_corner = Node(
            lat=center_node.lat - (r / earthR) * (180 / math.pi),
            lng=center_node.lng + (r / earthR) * (180 / math.pi) / math.cos(center_node.lat * math.pi / 180)
        )
        self.r = r

    def __str__(self):
        return 'center lat: {} lng: {} ; r {}m'.format(self.center.lat, self.center.lng, self.r)


class Way(object):
    """
    对应Openstreet map的way。但这里只考虑封闭的way。也就是area。
    使用一串有顺序的Node List来初始化
    """

    def __init__(self, nodes):
        for node in nodes:
            assert isinstance(node, Node)
        assert nodes[0] == nodes[-1]  # only closed ways
        self.nodes = []
        for i in range(len(nodes) - 1):  # exclude the last node
            self.nodes.append(nodes[i])

        """计算最大最小的经纬度、最大的覆盖Region"""
        min_lat = 91
        max_lat = -91
        min_lng = 181
        max_lng = -181
        for node in nodes:
            if min_lat > node.lat:
                min_lat = node.lat
            if max_lat < node.lat:
                max_lat = node.lat
            if min_lng > node.lng:
                min_lng = node.lng
            if max_lng < node.lng:
                max_lng = node.lng
        self.left_up_corner = Node(lat=max_lat, lng=min_lng)
        self.right_down_corner = Node(lat=min_lat, lng=max_lng)
        self.cover_region = Region(self.left_up_corner, self.right_down_corner)


class Region(object):
    """
    表示矩形区域
    矩形必须完全水平，这样我们就可以用左上角点和右下角点来描述它
    left_up表示最西北角，right_down表示最东南角
    使用Node类来初始化
    """

    def __init__(self, left_up, right_down):
        assert left_up.lat >= right_down.lat
        assert left_up.lng <= right_down.lng
        self.left_up = left_up
        self.right_down = right_down
        self.left_down = Node(lat=right_down.lat, lng=left_up.lng)
        self.right_up = Node(lat=left_up.lat, lng=right_down.lng)
        self.center = Node(lat=(left_up.lat + right_down.lat) / 2, lng=(left_up.lng + right_down.lng) / 2)
        '''y是指区域的某种feature'''
        self.y = 0

    def __eq__(self, region2):
        return (self.left_up == region2.left_up and self.right_down == region2.right_down)

    def in_(self, way):
        """
        近似处理，只要4个顶点都在way中，就算在way里面
        """
        if isinstance(way, Way):
            return (self.left_up.in_(way) and self.left_down.in_(way) and
                    self.right_up.in_(way) and self.right_down.in_(way))

    def center_in(self, way_or_circle):
        """
        另一种近似处理，只要中心在way / circle 中，就算在way / circle 中
        """
        if isinstance(way_or_circle, Way):
            return self.center.in_(way_or_circle)
        elif isinstance(way_or_circle, Circle):
            return self.center.in_(way_or_circle)
        else:
            raise NotImplementedError

    def area_fit_with(self, way_polygon):
        """
        感觉这种方式比较好。两者面积重合达到region的70%
        """
        region_polygon = Polygon([(self.left_up.lat, self.left_up.lng), (self.right_up.lat, self.right_up.lng),
                                  (self.right_down.lat, self.right_down.lng), (self.left_down.lat, self.left_down.lng)])
        region_area = region_polygon.area
        # logging.info(way_polygon.area)
        inter_area = way_polygon.intersection(region_polygon).area
        if inter_area / region_area >= 0.7 or inter_area / way_polygon.area >= 0.9:
            return True
        else:
            return False

    def to_way(self):
        nodes = []
        nodes.append(Node(lat=self.left_up.lat, lng=self.left_up.lng))
        nodes.append(Node(lat=self.right_down.lat, lng=self.left_up.lng))
        nodes.append(Node(lat=self.right_down.lat, lng=self.right_down.lng))
        nodes.append(Node(lat=self.left_up.lat, lng=self.right_down.lng))
        nodes.append(Node(lat=self.left_up.lat, lng=self.left_up.lng))
        return Way(nodes)

    def set_y(self, y):
        self.y = y


def point_in_polygon(x, y, verts):
    """
    - PNPoly算法
    - xyverts  [(x1, y1), (x2, y2), (x3, y3), ...]
    - origin: http://blog.leanote.com/post/iwantpython@163.com/d4d62c5dc860
    """
    try:
        x, y = float(x), float(y)
    except:
        return False
    vertx = [xyvert[0] for xyvert in verts]
    verty = [xyvert[1] for xyvert in verts]

    # N个点中，横坐标和纵坐标的最大值和最小值，判断目标坐标点是否在这个四边形之内
    if not verts or not min(vertx) <= x <= max(vertx) or not min(verty) <= y <= max(verty):
        return False

    # 上一步通过后，核心算法部分
    nvert = len(verts)
    is_in = False
    for i in range(nvert):
        j = nvert - 1 if i == 0 else i - 1
        if ((verty[i] > y) != (verty[j] > y)) and (
                x < (vertx[j] - vertx[i]) * (y - verty[i]) / (verty[j] - verty[i]) + vertx[i]):
            is_in = not is_in

    return is_in
